#if defined(_MSC_VER) || defined(__BORLANDC__)

#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

#if defined(_MSC_VER)
#define wcsncpy(d,s,l) wcsncpy_s((d), (l), (s), (l))
#define wcsncat(d,s,l) wcsncat_s((d), (l), (s), (l))
#endif

static wchar_t* sourceDirecroty;
static wchar_t* destinationDirecroty;
static bool preserveDeletedFiles;
static long copied;
static long deleted;
static long skipped;

// helpful directory's files container
class Files
{
	long size;

public:
	long count;
	WIN32_FIND_DATA * list;

	Files() {
		size = 16;
		count = 0;
		list = (WIN32_FIND_DATA*) malloc(sizeof(WIN32_FIND_DATA) * size);
	}
	~Files() {
		free(list);
	}

	void add(WIN32_FIND_DATA * file) {
		if (file) {
			if (count >= size) {
				size += 32;
				list = (WIN32_FIND_DATA*) realloc(list, sizeof(WIN32_FIND_DATA) * size);
			}
			memcpy(list + count, file, sizeof(WIN32_FIND_DATA));
			count++;
		}
	}

	void sort() {
		WIN32_FIND_DATA tmp;
		for (long i = 0; i < count - 1; i++) {
			for (long j = i; j < count; j++) {
				int cmp = wcscmp(list[i].cFileName, list[j].cFileName);
				if (cmp > 0) {
					memcpy(&tmp, list + i, sizeof(WIN32_FIND_DATA));
					memcpy(list + i, list + j, sizeof(WIN32_FIND_DATA));
					memcpy(list + j, &tmp, sizeof(WIN32_FIND_DATA));
				}
			}
		}
	}
};

void printError(wchar_t* text, wchar_t* file) {
	DWORD err = GetLastError();
	LPTSTR s;
	if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (LPTSTR) &s, 0, NULL)) {
		wprintf(L"%s%s\n       %s", (text == NULL) ? L"" : text, (file == NULL) ? L"" : file, s);
		LocalFree(s);
	}
}

void listDir(wchar_t* dir, Files* files) {
	const size_t len = wcslen(dir) + MAX_PATH + 8;
	wchar_t* filter = (wchar_t*) malloc(sizeof(wchar_t) * len);
	wcsncpy(filter, dir, len);
	wcsncat(filter, L"\\*", len);

	WIN32_FIND_DATA ffd;
	HANDLE hFind = FindFirstFile(filter, &ffd);

	if (INVALID_HANDLE_VALUE != hFind) {
		do {
			//skip current and previous dir
			if (ffd.cFileName[0] == '.' && (ffd.cFileName[1] == 0 || (ffd.cFileName[1] == '.' && ffd.cFileName[2] == 0))) {
				continue;
			}
			files->add(&ffd);
		} while (FindNextFile(hFind, &ffd) != 0);
	}
	FindClose(hFind);
	free(filter);
	files->sort();
}

BOOL removeUnwantedAttributes(wchar_t* file) {
	DWORD dwAttrs = GetFileAttributes(file);
	if (dwAttrs == INVALID_FILE_ATTRIBUTES) return false;
	const DWORD flags = (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
	if (dwAttrs & flags) {
		dwAttrs &= (DWORD) ~flags;
		SetFileAttributes(file, dwAttrs);
	}
	return true;
}

BOOL DeleteDirectory(wchar_t* lpPathName) {
	Files * files = new Files();
	listDir(lpPathName, files);

	const size_t len = wcslen(lpPathName) + MAX_PATH + 8;
	wchar_t* file = (wchar_t*) malloc(sizeof(wchar_t) * len);

	WIN32_FIND_DATA * list = files->list;
	for (int i = 0; i < files->count; i++) {
		WIN32_FIND_DATA* info = &list[i];

		wcsncpy(file, lpPathName, len);
		wcsncat(file, L"\\", len);
		wcsncat(file, info->cFileName, len);

		if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) {
			DeleteDirectory(file);
		} else {
			removeUnwantedAttributes(file);
			if (DeleteFile(file)) {
				deleted++;
			} else {
				printError(L"Cannot delete: ", file);
			}
		}
	}
	free(file);
	delete files;
	removeUnwantedAttributes(lpPathName);
	if (RemoveDirectory(lpPathName)) {
		return true;
	} else {
		printError(L"Cannot delete: ", lpPathName);
		return false;
	}
}

void logCoping(wchar_t* fileName) {
	const size_t len = wcslen(fileName);
	if (len < 71) {
		wprintf(L"Coping: %-71s\r", fileName);
	} else {
		wprintf(L"Coping: %.34s...%.34s\r", fileName, fileName + len - 34);
	}
}

void synch(wchar_t* sourceDir, wchar_t* destinationDir) {
	Files * srcFiles = new Files();
	listDir(sourceDir, srcFiles);

	Files * destFiles = new Files();
	listDir(destinationDir, destFiles);

	long s = 0, d = 0;
	const long sSize = srcFiles->count;
	const long dSize = destFiles->count;

	const size_t len1 = wcslen(sourceDir) + MAX_PATH + 8;
	wchar_t* file1 = (wchar_t*) malloc(sizeof(wchar_t) * len1);

	const size_t len2 = wcslen(destinationDir) + MAX_PATH + 8;
	wchar_t* file2 = (wchar_t*) malloc(sizeof(wchar_t) * len2);

	while (s < sSize || d < dSize) {
		WIN32_FIND_DATA * srcDATA = (s < sSize) ? (srcFiles->list + s) : NULL;
		WIN32_FIND_DATA * destDATA = (d < dSize) ? (destFiles->list + d) : NULL;

		if (s < sSize && d < dSize) {
			int cmp = wcscmp(srcDATA->cFileName, destDATA->cFileName);

			wcsncpy(file1, sourceDir, len1);
			wcsncat(file1, L"\\", len1);
			wcsncat(file1, srcDATA->cFileName, len1);

			if (cmp < 0) {
				wcsncpy(file2, destinationDir, len2);
				wcsncat(file2, L"\\", len2);
				wcsncat(file2, srcDATA->cFileName, len2);

				//new file
				if ((srcDATA->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) {
					CreateDirectory(file2, NULL);
					synch(file1, file2);
				} else {
					logCoping(file1);
					if (CopyFile(file1, file2, false)) copied++;
					else printError(L"Cannot copy: ", file1);
				}
				s++;
			} else if (cmp == 0) {
				wcsncpy(file2, destinationDir, len2);
				wcsncat(file2, L"\\", len2);
				wcsncat(file2, srcDATA->cFileName, len2);

				//same entry
				if ((srcDATA->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) {
					CreateDirectory(file2, NULL);
					synch(file1, file2);
				} else {
					FILETIME f1 = srcDATA->ftLastWriteTime;
					FILETIME f2 = destDATA->ftLastWriteTime;
					if (f1.dwLowDateTime != f2.dwLowDateTime || f1.dwHighDateTime != f2.dwHighDateTime) {
						logCoping(file1);
						removeUnwantedAttributes(file2);
						if (CopyFile(file1, file2, false)) copied++;
						else printError(L"Cannot overwrite: ", file2);
					} else {
						skipped++;
					}
				}
				s++;
				d++;
			} else {
				// entry was deleted in source dir
				if (!preserveDeletedFiles) {
					//remove if persist differences was not specified

					wcsncpy(file2, destinationDir, len2);
					wcsncat(file2, L"\\", len2);
					wcsncat(file2, destDATA->cFileName, len2);

					if ((destDATA->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) {
						DeleteDirectory(file2);
					} else {
						if (DeleteFile(file2)){ deleted++;}
						else{ printError(L"Cannot delete: ", file2);}
					}
				}
				d++;
			}
		} else if (d == dSize) {
			wcsncpy(file1, sourceDir, len1);
			wcsncat(file1, L"\\", len1);
			wcsncat(file1, srcDATA->cFileName, len1);

			wcsncpy(file2, destinationDir, len2);
			wcsncat(file2, L"\\", len2);
			wcsncat(file2, srcDATA->cFileName, len2);

			if ((srcDATA->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) {
				CreateDirectory(file2, NULL);
				synch(file1, file2);
			} else {
				logCoping(file1);
				if (CopyFile(file1, file2, false)) copied++;
				else printError(L"Cannot copy: ", file1);
			}
			s++;
		} else if (s == sSize) {
			// destination directory has more entries
			//remove them if persistence was not set
			if (!preserveDeletedFiles) {
				wcsncpy(file2, destinationDir, len2);
				wcsncat(file2, L"\\", len2);
				wcsncat(file2, destDATA->cFileName, len2);

				if ((destDATA->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0) {
					DeleteDirectory(file2);
				} else {
					if (DeleteFile(file2)) deleted++;
					else printError(L"Cannot delete: ", file2);
				}
				d++;
			} else {
				//preserve changes in destination
				d = dSize;
			}
		}
	}

	free(file1);
	free(file2);
	delete srcFiles;
	delete destFiles;
}

void mirror() {
	{//src name validation
		size_t len = wcslen(sourceDirecroty);
		if (len <= 0) {
			wprintf(L"source Direcroty is empty string\n");
			return;
		} else {
			len--;
			if (sourceDirecroty[len] == '\\') {
				sourceDirecroty[len] = 0;
			}
		}
	}
	{//dest name validation
		size_t len = wcslen(destinationDirecroty);
		if (len <= 0) {
			wprintf(L"destination Direcroty is empty string\n");
			return;
		} else {
			len--;
			if (destinationDirecroty[len] == '\\') {
				destinationDirecroty[len] = 0;
			}
		}
	}

	//is dir sourceDirecroty
	{
		DWORD attr = GetFileAttributes(sourceDirecroty);
		if ((attr == INVALID_FILE_ATTRIBUTES) || (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
			wprintf(L"source Direcroty is not a directory\n");
			return;
		}
	}

	//is dir destinationDirecroty
	{
		DWORD attr = GetFileAttributes(destinationDirecroty);
		if (attr == INVALID_FILE_ATTRIBUTES) {
			//try to create dir
			if (!CreateDirectory(destinationDirecroty, NULL)) {
				wprintf(L"cannot create destination Direcroty: \n");
				return;
			}
		} else if (attr && FILE_ATTRIBUTE_DIRECTORY == 0) {
			wprintf(L"destination Direcroty is not a directory\n");
			return;
		}
	}

	synch(sourceDirecroty, destinationDirecroty);
	wprintf(L"%ld copied, %ld skipped, %ld deleted %180s\n", copied, skipped, deleted, L"");
}

void printHelp(){
	wprintf(L"usage: mirror [-p] sourceDir destinationDir\n");
	wprintf(L"       Make the content of destinationDir to be the same as sourceDir.\n");
	wprintf(L"       Avoid coping of same files\n");
	wprintf(L"    -p Preserve files in destinationDir that are missing in sourceDir\n");
}

void char2wchar(CHAR * src, WCHAR* dest) {
	for (; *dest = *src; dest++, src++);
}

#if !defined(_MSC_VER)
// entry point for Borland C
int main(int argc, char* argv[]) {
	WCHAR arg1[MAX_PATH];
	WCHAR arg2[MAX_PATH];

	if ((argc == 4 && strcmp("-p", argv[1]) == 0) || argc == 3) {
		preserveDeletedFiles = (argc == 4);
		char2wchar(argv[argc - 2], arg2);
		sourceDirecroty = arg2;
		char2wchar(argv[argc - 1], arg1);
		destinationDirecroty = arg1;

		wprintf(L"---- MIRROR ----\n");
		wprintf(L"Source Dir      [%s]\n", sourceDirecroty);
		wprintf(L"Destination Dir [%s] ", destinationDirecroty);
		if (argc == 4) {
			wprintf(L" preserve files");
		}
		wprintf(L"\n");

		// do the synchronization
		mirror();

	} else {
		printHelp();
	}
	return 0;
}

#endif

#if defined(_MSC_VER)
// entry point for Visual C
int wmain(int argc, wchar_t* argv[]) {
	if ((argc == 4 && wcscmp(L"-p", argv[1]) == 0) || argc == 3) {
		preserveDeletedFiles = (argc == 4);
		sourceDirecroty = argv[argc - 2];
		destinationDirecroty = argv[argc - 1];

		wprintf(L"---- MIRROR ----\n");
		wprintf(L"Source Dir      [%s]\n", sourceDirecroty);
		wprintf(L"Destination Dir [%s] ", destinationDirecroty);
		if (argc == 4) {
			wprintf(L" preserve files");
		}
		wprintf(L"\n");

		// do the synchronization
		mirror();

	} else {
		printHelp();
	}
	return 0;
}
#endif

#endif
